/////////////////////////////////////////////////////////////////////////////
// Purpose:     Concurrent light-weight processes management
// Author:      Jesus Gonzalez
// Modified by: 
// Copyright:   (c) 2003-2013 Jesus Gonzalez <jgonzalez@gdr-sistemas.com>
// License:     BSD License
/////////////////////////////////////////////////////////////////////////////

#include "SmartLib/Thread.h"
#include "Log.h"

#include <pthread.h>
#include <signal.h>
#include <errno.h>
#include <string.h>

class ThreadPrvSys
{
public:
    ThreadPrvSys() : thread_id( (pthread_t)-1 ) {}

    static void* EntryHandler(void* arg)
    {
        Thread* thread = (Thread*) arg;

        return (void*) thread->EntryPoint();
    }

    pthread_t thread_id;
};

Thread::Thread(bool runNow, bool detached)
: m_prvsys(new ThreadPrvSys)
{
    if( runNow )
    {
        Run(detached);
    }
}

Thread::~Thread()
{
    delete m_prvsys;
}

void Thread::Run(bool detached)
{
    if( m_prvsys->thread_id != -1 )
    {
        THROW_ERROR("The thread is already running");
    }

    pthread_attr_t attr;

    if( pthread_attr_init(&attr) != 0 )
    {
        THROW_ERROR("Couldn't initialize thread attributes (Error = %s)", strerror(errno));
    }

    if( pthread_attr_setdetachstate(&attr, detached ? PTHREAD_CREATE_DETACHED : PTHREAD_CREATE_JOINABLE) != 0 )
    {
        pthread_attr_destroy(&attr);
        THROW_ERROR("Couldn't set thread detached state (Error = %s)", strerror(errno));
    }

    if( pthread_create(&m_prvsys->thread_id, &attr, ThreadPrvSys::EntryHandler, this) != 0 )
    {
        pthread_attr_destroy(&attr);
        THROW_ERROR("Couldn't create thread (Error = %s)", strerror(errno));
    }

    // Attributes are no longer needed, we can destroy them.
    pthread_attr_destroy(&attr);
}

bool Thread::Wait(long &exitStatus, bool noblock)
{
    if( m_prvsys->thread_id == -1 )
    {
        THROW_ERROR("The thread is not running");
    }

    if( noblock )
    {
#ifndef NO_PTHREAD_TRYJOIN
        if( pthread_tryjoin_np(m_prvsys->thread_id, (void**)&exitStatus) != 0 )
        {
            if( errno == EBUSY ) return false;
            THROW_ERROR("Couldn't wait for thread (Error = %s)", strerror(errno));
        }
#else
        if( isRunning() ) return false;

        if( pthread_join(m_prvsys->thread_id, (void**)&exitStatus) != 0 )
        {
            THROW_ERROR("Couldn't wait for thread (Error = %s)", strerror(errno));
        }
#endif
    }
    else
    {
        if( pthread_join(m_prvsys->thread_id, (void**)&exitStatus) != 0 )
        {
            THROW_ERROR("Couldn't wait for thread (Error = %s)", strerror(errno));
        }
    }

    return true;
}

bool Thread::isRunning() const
{
    if( pthread_kill(m_prvsys->thread_id, 0) != 0 )
    {
        if( errno == ESRCH ) return false;

        THROW_ERROR("Couldn't send signal to thread (Error = %s)", strerror(errno));
    }

    return true;
}
